package com.tool.emulator.apple2;

import com.tool.emulator.apple2.runtime.EmuState;
import com.tool.emulator.common.AutoDevice;
import com.tool.emulator.common.IODevice;
import com.tool.emulator.mos6502.CPU6502;

import java.io.*;

/**
 * Recorder.java @author tl 2011-6-20
 */
public class Recorder extends AutoDevice implements IODevice {
	protected final Apple2 apple2;
	protected final CPU6502 cpu;
	private final FlipFlop ff;
	private volatile DataInputStream ins = null;
	private volatile DataOutputStream outs = null;
	private long nextInput = -1;
	private volatile long lastAccessTime = -1;
	private volatile boolean closeWhenIdle = false;
	private long outputStartCycles = -1;
	private long lastOutputCycles = -1;

	public Recorder(Apple2 apple2) {
		super("Recoder", 10);
		apple2.BUS.initIO(Apple2.ADDR_RECORDER_OUT, 1, this);
		apple2.BUS.initIO(Apple2.ADDR_RECORDER_IN, 1, this);
		this.apple2 = apple2;
		this.cpu = apple2.CPU;
		this.ff = new FlipFlop(cpu);
	}

	@Override
	public void deviceSaveState(EmuState emuState) throws Exception {
		//ignore
		return;
	}

	@Override
	public void deviceLoadState(EmuState emuState) throws Exception {
		this.closeAll();
	}

	protected synchronized int input() {
		long cycles = cpu.getCycles();
		if (this.ins != null) {
			this.closeWhenIdle = true;
			if (this.nextInput < 0)
				this.nextInput = cycles;
			while (this.nextInput <= cycles) {
				ff.set(this.nextInput);
				long next = this.readInput();
				if (next >= 0) {
					this.nextInput += next;
					if (this.nextInput > cycles && apple2.RECORDER_IN_TO_SPEAKER)
						apple2.SPEAKER.flipFlop(this.nextInput);

				} else {
					this.nextInput = -1;
					break;
				}
			}
		}
		return ff.get(cycles) ? 0x00 : 0xff;
	}

	private int readInput() {
		try {
			DataInputStream in = this.ins;
			if (in != null) {
				synchronized (in) {
					int val = in.readShort();
					if (val >= 0)
						return val;
					int val1 = in.readShort();
					val = (val << 16) | (val1 & 0xffff);
					if (val >= 0)
						throw new IOException("data invalid");
					return -val;
				}
			}
		} catch (Exception ex) {
			this.stop();
			if (!(ex instanceof EOFException))
				ex.printStackTrace();
		}
		return -1;
	}

	protected synchronized void output() {
		long cycles = cpu.getCycles();
		this.ff.set(cycles);
		if (apple2.RECORDER_OUT_TO_SPEAKER)
			apple2.SPEAKER.flipFlop(cycles);
		if (this.outs == null) {
			this.lastOutputCycles = this.outputStartCycles = -1;
		} else {
			this.closeWhenIdle = true;
			if (this.outputStartCycles < 0) {
				this.lastOutputCycles = this.outputStartCycles = cycles;
			}
			try {
				long delta = cycles - this.lastOutputCycles;
				this.lastOutputCycles = cycles;
				synchronized (this.outs) {
					if (delta < 0) delta = 0;
					if (delta <= (long) Short.MAX_VALUE)
						this.outs.writeShort(0x7fff & ((int) delta));
					else {
						delta = -delta;
						if (delta < (long) Integer.MIN_VALUE)
							delta = Integer.MIN_VALUE;
						this.outs.writeInt((int) delta);
					}
				}
			} catch (Exception ex) {
				this.stop();
			}
		}
	}

	@Override
	protected boolean deviceStep() {
		if (this.closeWhenIdle && System.currentTimeMillis() - this.lastAccessTime > 1000L)
			this.stop();
		this.current_cycles++;
		return true;
	}

	public int read_io(int addr) {
		lastAccessTime = System.currentTimeMillis();
		if (addr == Apple2.ADDR_RECORDER_IN)
			return input();
		else if (addr == Apple2.ADDR_RECORDER_OUT)
			output();
		return 0;
	}

	public void write_io(int addr, int val) {
		//twice read
		this.read_io(addr);
		this.read_io(addr);
	}

	public synchronized void startRec(DataOutputStream out) {
		closeAll();
		this.outs = out;
		this.outputStartCycles = -1;
	}

	public synchronized void stop() {
		closeAll();
	}

	public synchronized void startPlay(DataInputStream in) {
		closeAll();
		this.ins = in;
	}

	public synchronized boolean isPlaying() {
		return this.ins != null;
	}

	public synchronized boolean isRecording() {
		return this.outs != null;
	}

	public synchronized boolean isStoped() {
		return this.ins == null && this.outs == null;
	}

	@Override
	protected void deviceInit() throws Exception {
		super.deviceInit();
		this.ff.reset();
	}

	@Override
	protected void deviceExit() throws Exception {
		super.deviceExit();
		closeAll();
	}

	private <T extends Closeable> T closeX(T x) {
		try {
			if (x != null) {
				synchronized (x) {
					x.close();
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	private synchronized void closeAll() {
		this.ins = closeX(this.ins);
		this.outs = closeX(this.outs);
		this.outputStartCycles = -1;
		this.nextInput = -1;
		this.closeWhenIdle = false;
	}
}
